#include <stdlib.h> 
#include <string.h>

#include <pthread.h>
#include <errno.h>
#include <unistd.h>
#include <cm/struct/cm_fifo.h>

struct cm_fifo {
	char *data; /* 数据 */
	unsigned size; /* 数据总大小 */

	unsigned readable; /* 可读字节数 */
	unsigned wpos;
	unsigned rpos; /* 读写的位置 */

	pthread_mutex_t lock;
};

/* @brief	判断位置关系
 * @return	非0表示位置关系为:  |---r---w----|size
 * 		0表示位置关系为:    |---w---r----|size
 */
static inline int _rwposition(const struct cm_fifo *fifo)
{
	return (fifo->rpos <= fifo->wpos);
}

inline int cmfifo_empty(struct cm_fifo *fifo)
{
    int ret = 0;
    pthread_mutex_lock(&(fifo->lock));
    ret = fifo->rpos == fifo->wpos;
    pthread_mutex_unlock(&(fifo->lock));
    return (ret);
}

inline unsigned cmfifo_readable(struct cm_fifo *fifo)
{
    int ret = 0;
    pthread_mutex_lock(&(fifo->lock));
    ret = (fifo->readable);
    pthread_mutex_unlock(&(fifo->lock));
    return ret;
}

inline unsigned cmfifo_space(struct cm_fifo *fifo)
{
    int ret = 0;
    pthread_mutex_lock(&(fifo->lock));
    ret = (fifo->size - fifo->readable);
    pthread_mutex_unlock(&(fifo->lock));
    return ret;
}

inline unsigned cmfifo_size(cmfifo_t *fifo)
{
    int ret = 0;
    pthread_mutex_lock(&(fifo->lock));
    ret = (fifo->size);
    pthread_mutex_unlock(&(fifo->lock));
	return ret;
}

/*
 * @brief	在进行了读或写操作以后，设置FIFO内部的各项标识位和状态位
 * @param	op: 指示是进行了读还是写操作
 * @param	size: 一共操作了多少数据
 * @return	0 成功， -1 失败
 */
inline int cmfifo_setstatus(cmfifo_t *fifo, enum CMFIFO_OP op, unsigned size)
{
    if (size == 0)
        return 0;

    pthread_mutex_lock(&(fifo->lock));
    if (op == CMFIFO_PUT) {
        if (cmfifo_space(fifo) < size) {
            pthread_mutex_unlock(&(fifo->lock));
            return -1;
        }
        fifo->wpos = ((fifo->wpos + size) % fifo->size);
        fifo->readable += size;
    } else if (op == CMFIFO_GET) {
        if (fifo->readable < size) {
            pthread_mutex_unlock(&(fifo->lock));
            return -1;
        }
        fifo->rpos = ((fifo->rpos + size) % fifo->size);
        fifo->readable -= size;
    }
    pthread_mutex_unlock(&(fifo->lock));
    return 0;
}

/*
 * @brief	reset a FIFO, clear it's data
 */
inline void cmfifo_reset(cmfifo_t *pfifo)
{
    if (pfifo == NULL)
        return;
    pthread_mutex_lock(&(pfifo->lock));
    pfifo->readable = 0;
    pfifo->wpos = 0;
    pfifo->rpos = 0;
    pthread_mutex_unlock(&(pfifo->lock));
}

/*
 * @brief	create a FIFO and set it's size to 'size'
 * @return	A pointer to the new FIFO or NULL for failed.
 */
inline cmfifo_t *cmfifo_create(unsigned s)
{
	if (s == 0)
		return NULL;

	cmfifo_t *ptr = (cmfifo_t *) calloc(1, sizeof(struct cm_fifo) + s);

	if (ptr == NULL)
		return NULL;

	ptr->data = (char *) (ptr + 1);
	ptr->size = s;
    pthread_mutex_init(&(ptr->lock), NULL);
	return ptr;
}

inline void cmfifo_destroy(cmfifo_t **pfifo)
{
    if (pfifo == NULL || *pfifo == NULL)
        return;
    pthread_mutex_destroy(&((*pfifo)->lock));
    free(*pfifo);
    *pfifo = NULL;
}

/*
 * @brief	put some data into a FIFO.
 * @return	if there is enough space for putting, then save the data and return 0 for success,
 * 		return -1 for error
 */
int cmfifo_putdata(cmfifo_t *pfifo, const char *src, unsigned s)
{
	if (pfifo == NULL || src == NULL || s == 0)
		return -1;

	if (s > cmfifo_space(pfifo))
		return -1; /* no memory */

	pthread_mutex_lock(&(pfifo->lock));

	if (_rwposition(pfifo)) {
		/* 可能需要分两步拷贝,根据s大小及wpos的位置来确定 */
		unsigned temp = pfifo->size - pfifo->wpos;
		if (temp >= s) {
			memcpy(pfifo->data + pfifo->wpos, src, s);
		} else {
			memcpy(pfifo->data + pfifo->wpos, src, temp);
			memcpy(pfifo->data, src + temp, s - temp);
		}
	} else {
		memcpy(pfifo->data + pfifo->wpos, src, s);
	}
	pfifo->wpos = ((pfifo->wpos + s) % pfifo->size);
	pfifo->readable += s;

	pthread_mutex_unlock(&(pfifo->lock));
	return 0;
}

/*
 * @brief	从队列中取出数据
 * @param	pfifo: 某队列的对象
 * @param	dest: 用来存放拷贝数据的位置
 * @param	s: 指定需要取出数据的大小
 * @param	popout: 取出完成后，是否将数据移出队列
 * @return	成功返回0 失败返回-1
 */
static int _getdata(cmfifo_t *pfifo, char *dest, unsigned s, char popout)
{
	if (pfifo == NULL || dest == NULL || s == 0)
		return -1;

	if (pfifo->readable < s)
		return -1;

	pthread_mutex_lock(&(pfifo->lock));

	if (_rwposition(pfifo)) {
		memcpy(dest, pfifo->data + pfifo->rpos, s);
	} else {
		unsigned temp = pfifo->size - pfifo->rpos;
		if (temp >= s) {
			memcpy(dest, pfifo->data + pfifo->rpos, s);
		} else {
			memcpy(dest, pfifo->data + pfifo->rpos, temp);
			memcpy(dest + temp, pfifo->data, s - temp);
		}
	}

	if (popout) {
		pfifo->rpos = ((pfifo->rpos + s) % pfifo->size);
		pfifo->readable -= s;
	}
	pthread_mutex_unlock(&(pfifo->lock));

	return 0;
}

/*
 * @brief	get 'size' bytes of data from a FIFO, and copy it into data
 * @brief	return 0 for success, -1 for error (not enough data for getting or other errors)
 */
int cmfifo_getdata(cmfifo_t *pfifo, char *dest, unsigned s)
{
	return _getdata(pfifo, dest, s, 1);
}

int cmfifo_tryget(cmfifo_t *pfifo, char *dest, unsigned s)
{
	return _getdata(pfifo, dest, s, 0);
}

/*
 * @brief	直接获取FIFO中的数据指针，因为存储的内部形式，所以完整的数据需要两个指针来表示
 * @param	通过ptr1和size1返回第一个指针所指向的数据和数据大小, 通过ptr2和size2返回第二部分数据和大小
 * 		如果该部分没有数据则返回NULL和0, 第一部分没有数据则第二部分一定没有数据
 * @return	0表示成功， -1表示失败
 */
int cmfifo_dataptr(cmfifo_t *fifo, char **ptr1, unsigned *size1, char **ptr2,
		unsigned *size2)
{
	if (cmfifo_empty(fifo))
		return -1;

	*(ptr1) = (fifo->data + fifo->rpos);
	if (_rwposition(fifo)) {
		*size1 = fifo->readable;
		*(ptr2) = NULL;
		*size2 = 0;
	} else {
		*size1 = fifo->size - fifo->rpos;
		*(ptr2) = fifo->data;
		*size2 = fifo->readable - *size1;
	}
	return 0;
}

/*
 * @brief   直接获取FIFO中可以用来存放数据的空间的指针，因为存储的内部形式，所以完整的空间需要两个指针来表示
 * @param   通过ptr1和size1返回第一个指针所指向的空间和空间大小, 通过ptr2和size2返回第二部分空间和大小
 *      如果该部分没有空间则返回NULL和0, 第一个指针为NULL则第二个一定为NULL
 * @return  0表示成功， -1表示失败
 */
int cmfifo_spaceptr(cmfifo_t *fifo, char **ptr1, unsigned *size1, char **ptr2,
        unsigned *size2)
{
    if (cmfifo_space(fifo) == 0)
        return -1;

    *(ptr1) = (fifo->data + fifo->wpos);
    if (_rwposition(fifo)) {
        //        data|---r===w----|size
        *size1 = fifo->size - fifo->wpos;
        *(ptr2) = fifo->data;
        *size2 = fifo->rpos;
    } else {
        //        data|===w---r====|size
        *size1 = cmfifo_space(fifo);
        *(ptr2) = NULL;
        *size2 = 0;
    }
    return 0;
}
